package biz.datalk.industrialland.common.component;

import biz.datalk.commons.utils.json.JsonUtil;
import biz.datalk.industrialland.common.advice.AppSecurityInfo;
import biz.datalk.industrialland.common.advice.cfg.DatalkCryptoCfgs;
import biz.datalk.industrialland.common.advice.mapper.IAppEncryptSecurityKeyMapper;
import biz.datalk.industrialland.common.util.RedisUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 应用加解密秘钥获取组件
 *
 * @author tarofang@163.com
 * @date 2022-10-08
 */
public class AppEncryptSecurityKeyComponent {
    private static final Logger log = LoggerFactory.getLogger(AppEncryptSecurityKeyComponent.class);
    public static final String CACHE_KEY_PREFIX_APP_ENCRYPT_SECURITY_KEY = "APP_ENCRYPT_SECURITY_KEY:";
    public static final String CACHE_KEY_PATTERN_APP_ENCRYPT_SECURITY_KEY = CACHE_KEY_PREFIX_APP_ENCRYPT_SECURITY_KEY + "%s";

    public static final String CACHE_NONE = "__NONE__";

    private final RedisUtil redisUtil;
    private final IAppEncryptSecurityKeyMapper appEncryptSecurityKeyMapper;
    private final DatalkCryptoCfgs datalkCryptoCfgs;

    public AppEncryptSecurityKeyComponent(RedisUtil redisUtil, IAppEncryptSecurityKeyMapper appEncryptSecurityKeyMapper, DatalkCryptoCfgs datalkCryptoCfgs) {
        this.redisUtil = redisUtil;
        this.appEncryptSecurityKeyMapper = appEncryptSecurityKeyMapper;
        this.datalkCryptoCfgs = datalkCryptoCfgs;
    }

    private AppSecurityInfo appSecurityInfo;

    private long expireSeconds;
    private long expireMaxRandom;

    private boolean cleanDisableAppKeyWhenInitCache;

    private boolean useDynamicKey;


    @Value("${app_encrypt_security_info_expire_seconds:7200}")
    public void setExpireSeconds(long expireSeconds) {
        this.expireSeconds = expireSeconds;
    }

    @Value("${app_encrypt_security_info_expire_seconds_max_random:1800}")
    public void setExpireMaxRandom(long expireMaxRandom) {
        this.expireMaxRandom = expireMaxRandom;
    }

    @Value("${app_encrypt_security_info_clean_disable_appkey_when_init_cache:false}")
    public void setCleanDisableAppKeyWhenInitCache(boolean cleanDisableAppKeyWhenInitCache) {
        this.cleanDisableAppKeyWhenInitCache = cleanDisableAppKeyWhenInitCache;
    }

    @Value("${app_encrypt_security_info_use_dynamic_key:false}")
    public void setUseDynamicKey(boolean useDynamicKey) {
        this.useDynamicKey = useDynamicKey;
    }

    public String getCacheKey(String appKey) {
        String tmpAppKey = StringUtils.EMPTY;
        if (StringUtils.isNotBlank(appKey)) {
            tmpAppKey = appKey.trim();
        }
        String cacheKey = String.format(CACHE_KEY_PATTERN_APP_ENCRYPT_SECURITY_KEY, tmpAppKey);
        if (log.isDebugEnabled()) {
            log.debug("[appKey={}, cacheKey={}]", appKey, cacheKey);
        }
        return cacheKey;
    }

    public void initCache() {
        if (log.isDebugEnabled()) {
            log.debug("[useDynamicKey={}]", useDynamicKey);
        }
        if (!this.useDynamicKey) {
            return;
        }
        // 从数据库中获取数据
        List<AppSecurityInfo> appSecurityInfoList = this.appEncryptSecurityKeyMapper.selectAppInfos();
        if (CollectionUtils.isEmpty(appSecurityInfoList)) {
            return;
        }

        // 放入缓存
        String cacheKey, cacheJson;
        long expireSeconds;
        List<String> saveCacheAppKeyList = new ArrayList<>();
        for (AppSecurityInfo appSecurityInfo : appSecurityInfoList) {
            cacheKey = this.getCacheKey(appSecurityInfo.getAppKey());
            cacheJson = JsonUtil.getJsonFromObject(appSecurityInfo);
            expireSeconds = this.expirePlusRandom();
            redisUtil.set(cacheKey, cacheJson, expireSeconds, TimeUnit.SECONDS);
            if (log.isTraceEnabled()) {
                log.trace("set cache: [cacheKey={}, cacheJson={}, expireSeconds={}]", cacheKey, cacheJson, expireSeconds);
            }
            saveCacheAppKeyList.add(cacheKey);
        }


        // 清理不存在的应用的缓存
        if (this.cleanDisableAppKeyWhenInitCache) {
            String pattern = String.format(CACHE_KEY_PATTERN_APP_ENCRYPT_SECURITY_KEY, "*");
            RedisSerializer<String> keySerializer = (RedisSerializer<String>) redisUtil.getRedisTemplate().getKeySerializer();
            pattern = new String(keySerializer.serialize(pattern), StandardCharsets.UTF_8);
            redisUtil.scan(pattern, key -> {
                if (!saveCacheAppKeyList.contains(key)) {
                    redisUtil.del(key);
                    log.info("delete key: {}", key);
                }
            });
        }
    }

    public AppSecurityInfo getByAppKey(String appKey) {
        if (log.isDebugEnabled()) {
            log.debug("[useDynamicKey={}]", useDynamicKey);
        }
        if (!this.useDynamicKey) {
            if (this.appSecurityInfo == null) {
                AppSecurityInfo appSecurityInfo = new AppSecurityInfo();
                appSecurityInfo.setId(0L);
                appSecurityInfo.setAppKey(appKey);
                appSecurityInfo.setClientPublicKey(datalkCryptoCfgs.getClient().getPublicKey());
                appSecurityInfo.setClientPrivateKey(datalkCryptoCfgs.getClient().getPrivateKey());
                appSecurityInfo.setServerPublicKey(datalkCryptoCfgs.getServer().getPublicKey());
                appSecurityInfo.setServerPrivateKey(datalkCryptoCfgs.getServer().getPrivateKey());
                this.appSecurityInfo = appSecurityInfo;
            }
            return this.appSecurityInfo;
        }

        String tmpAppKey = StringUtils.trimToNull(appKey);
        if (tmpAppKey == null) {
            return null;
        }
        // 先从缓存中获取
        String cacheKey = this.getCacheKey(tmpAppKey);
        String cache = redisUtil.getStr(cacheKey, StringUtils.EMPTY);
        if (StringUtils.equals(cache, CACHE_NONE)) {
            return null;
        }

        if (StringUtils.isNotBlank(cache)) {
            AppSecurityInfo info = JsonUtil.json2JavaBean(cache, AppSecurityInfo.class);
            if (info != null) {
                return info;
            }
        }

        // 在从数据库中获取 并 放入缓存
        AppSecurityInfo infoOnDb = this.appEncryptSecurityKeyMapper.selectAppInfoByAppKey(tmpAppKey);
        String cacheJson = infoOnDb == null ? CACHE_NONE : JsonUtil.getJsonFromObject(infoOnDb);
        long expireSeconds = this.expirePlusRandom();
        redisUtil.set(cacheKey, cacheJson, expireSeconds, TimeUnit.SECONDS);
        log.trace("set cache: [cacheKey={}, cacheJson={}, expireSeconds={}]", cacheKey, cacheJson, expireSeconds);

        return infoOnDb;

    }

    private long expirePlusRandom() {
        return this.expireSeconds + RandomUtils.nextLong(0, this.expireMaxRandom);
    }


}
